// tsc src/feats/test.ts --t es2020 --moduleResolution node
const microQueen: (() => void)[] = [];
type Handler = {
  thenFn?: (res: unknown) => unknown;
  catchFn?: (rea: unknown) => unknown;
  finallyFn?: () => unknown;

  nextResolve: (res: unknown) => void;
  nextReject: (rea: unknown) => void;

  handled: boolean;
};

class MyPromise {
  static resolve(data?: unknown): MyPromise {
    return new MyPromise((res) => {
      res(data);
    });
  }

  static reject(data?: unknown): MyPromise {
    return new MyPromise((_, rej) => {
      rej(data);
    });
  }

  #status: 'fulfilled' | 'rejected' | 'pending' = 'pending';
  #rea_res: unknown = undefined;

  #handlers: Handler[] = [];

  #onSettled(handler: Handler) {
    /**
     * 关键点1：函数执行
     *  thenFn finallyFn 只会存在一个，或都不存在;
     *  catchFn finallyFn 只会存在一个，或都不存在 ;
     *  catchFn thenFn 可能同时存在，但只会执行其中一个;
     *  三个方法至少存在一个，但考虑到status情况，三个方法可能都不执行
     * 关键点2：下一链的状态
     *  基于关键点1，next的status和res_rea默认继承当前的，
     *  如果执行了thenFn或catchFn，则成功返回一个值或产生的异常
     *  无论那种情况，都将作为新的next的res_rea，成功返回 新的status为fulfilled，产生异常 新的status是rejected
     * 关键点3：处理下一链
     *  关键点2中忽略了返回Promise的情况，所以最后判断。
     *  如果是这种情况，直接将nextResolve和nextReject作为该返回promise的next值即可。一切交给递归。
     *  当然如果不是promise，那么应该立即执行两者中的一个。
     * 关键点4：异常
     *  异常存在于每次可能的执行（构造器，then，catch，finally)，都要做异常捕获置为rejected
     *  此时必须调用catch，否则最后都要真正的throw这个异常
     * 关键点5：异常必须catch或对外抛出
     *  一个promise存在异常（rejected），由它延伸出的每条调用链都必须处理，否则外界将收到异常
     *  且至少有一个链处理吧！
     *
     * 经过测试：then，catch，finally里面的执行都是异步的，都是放到下一个微任务中执行的
     */

    let nextState: 'fulfilled' | 'rejected' | 'pending' = this.#status;
    let nextRes_Rea: unknown = this.#rea_res;

    try {
      if (this.#status === 'fulfilled' && handler.thenFn) {
        nextRes_Rea = handler.thenFn(this.#rea_res);
        nextState = 'fulfilled';
      }
      if (this.#status === 'rejected' && handler.catchFn) {
        nextRes_Rea = handler.catchFn(this.#rea_res);
        nextState = 'fulfilled';
      }
      if (handler.finallyFn) {
        handler.finallyFn();
      }
    } catch (e) {
      nextRes_Rea = e;
      nextState = 'rejected';
    }

    // -----------------------------------------------
    // 处理下一链（异常不会是promise吧！可能是，所以要加判断）
    // promise情况：移花接木，后续链接到新的promise上（接到nextRes_Rea上）
    // 剩余：异常情况 或 普通值的fulfilled.
    // 普通的fulfilled，直接处理；异常情况，要么成功转移到下一个，要么抛出
    if (nextRes_Rea instanceof MyPromise && nextState === 'fulfilled') {
      const nextHandler: Handler = {
        nextReject: handler.nextReject,
        nextResolve: handler.nextResolve,
        handled: false,
      };
      nextRes_Rea.#handlers.push(nextHandler);
      if (nextRes_Rea.#status !== 'pending') {
        nextRes_Rea.#flush();
      }
      return;
    }

    if (nextState === 'fulfilled') {
      handler.nextResolve?.(nextRes_Rea);
      return;
    }

    if (handler.nextReject) {
      handler.nextReject(nextRes_Rea);
    } else {
      throw nextRes_Rea;
    }
  }

  constructor(fn: (resolve: (res: unknown) => void, reject: (rea: unknown) => void) => unknown) {
    const resolve: (res: unknown) => void = (res) => {
      this.#status = 'fulfilled';
      this.#rea_res = res;
      this.#flush();
    };
    const reject: (res: unknown) => void = (rea) => {
      this.#status = 'rejected';
      this.#rea_res = rea;
      this.#flush();
    };

    try {
      // 为什么要首先在构造其中产生一个特殊的handler？
      // 考虑作为最后一个promise，没有处理异常的情况 ，需要抛出异常，这在onSettled中已经实现。
      // 我们要做的只是，添加一个没有nextReject的handler，即这里的特殊handler
      // 因为：没有handler flush循环将不会执行任何代码
      //
      // 当然如果不是最后一个，那么必然通过then、catch、finally方法中的一个，产生了一个正常的handler。 onSettled会将异常的处理转移过去
      // 注意flush，当同步代码执行完后，只有一个handler时，那必然是这个特殊的handler；如果有多个，那一定要移除这个handler，因为异常转移到下一个环节了。

      this.#handlers.push({ handled: false } as Handler);

      fn(resolve, reject);
    } catch (e) {
      this.#status = 'rejected';
      this.#rea_res = e;
      this.#flush();
    }
  }

  #flush() {
    microQueen.push(() => {
      if (this.#handlers.length > 1) {
        this.#handlers.shift();
      }
      while (this.#handlers.length) microQueen.push(this.#onSettled.bind(this, this.#handlers.shift()));
    });
  }

  #getNextPromise(h: Partial<Handler>): MyPromise {
    const pro = new MyPromise((resolve, reject) => {
      const handler: Handler = {
        ...h,
        nextReject: reject,
        nextResolve: resolve,
        handled: false,
      };
      this.#handlers.push(handler);
      if (this.#status !== 'pending') {
        this.#flush();
      }
    });
    return pro;
  }

  then(fn1: (res: unknown) => unknown, fn2?: (rea: unknown) => unknown): MyPromise {
    return this.#getNextPromise({ thenFn: fn1, catchFn: fn2 });
  }

  catch(fn: (rea: unknown) => unknown): MyPromise {
    return this.#getNextPromise({ catchFn: fn });
  }

  finally(fn: () => unknown): MyPromise {
    return this.#getNextPromise({ finallyFn: fn });
  }
}

const timerId = setInterval(() => {
  const fn = microQueen.shift();
  if (fn) {
    fn();
  } else {
    clearInterval(timerId);
  }
}, 10);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const w: any = globalThis || { MyPromise: Function };
w.MyPromise = MyPromise;
